package com.ajavaer.framework.pay;

import com.ajavaer.framework.common.message.Message;
import com.ajavaer.framework.common.tools.JsonTools;
import com.ajavaer.framework.common.tools.ServletTools;
import com.ajavaer.framework.common.tools.StringTools;
import com.ajavaer.framework.config.AlipayConfig;
import com.ajavaer.framework.handle.AlipayHandle;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayResponse;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.*;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static com.ajavaer.framework.common.tools.StringTools.isBlank;

@Component
public class Alipay implements InitializingBean {
    private Logger log = LoggerFactory.getLogger(Alipay.class);
    @Resource
    private AlipayConfig alipayConfig;
    @Resource
    private Environment environment;
    private Boolean isDebug = true;

    private AlipayClient alipayClient;

    private ExecutorService executor = Executors.newScheduledThreadPool(20);


    /**
     * 外部商户APP唤起快捷SDK创建订单并支付
     * <a href='https://docs.open.alipay.com/api_1/alipay.trade.app.pay/'>文档</a>
     *
     * @param model <br>
     *              - subject [Y]	商品的标题/交易标题/订单标题/订单关键字等。<br>
     *              - out_trade_no [Y] 商户网站唯一订单号<br>
     *              - total_amount [Y] 订单总金额，单位为元，精确到小数点后两位，取值范围[0.01,100000000]<br>
     *              - timeout_express 该笔订单允许的最晚付款时间，逾期将关闭交易。取值范围：1m～15d。m-分钟，h-小时，d-天，1c-当天（1c-当天的情况下，无论交易何时创建，都在0点关闭）。 该参数数值不接受小数点， 如 1.5h，可转换为 90m。<br>
     *              - product_code 销售产品码，商家和支付宝签约的产品码<br>
     *              - body 对一笔交易的具体描述信息。如果是多种商品，请将商品描述字符串累加传给body。<br>
     *              - time_expire 绝对超时时间，格式为yyyy-MM-dd HH:mm。<br>
     *              - goods_type 商品主类型 :0-虚拟类商品,1-实物类商品<br>
     *              - promo_params 优惠参数 注：仅与支付宝协商后可用<br>
     *              - passback_params 公用回传参数，如果请求时传递了该参数，则返回给商户时会回传该参数。支付宝只会在同步返回（包括跳转回商户网站）和异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝。例如:merchantBizType%3d3C%26merchantBizNo%3d2016010101111<br>
     *              - business_params 	商户传入业务信息，具体值要和支付宝约定，应用于安全，营销等参数直传场景，格式为json格式 例如:{"data":"123"}<br>
     * @return
     */
    public Message<AlipayTradeAppPayResponse> tradeAppPay(AlipayTradeAppPayModel model) {
        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
        request.setBizModel(model);
        request.setNotifyUrl(alipayConfig.getNotifyUrl());
        try {
            if (isDebug) {
                log.debug("[Pay Alipay] refund tradeAppPay:" + JsonTools.beanToJson(model));
            }
            request.setNeedEncrypt(alipayConfig.needEncrypt());
            AlipayTradeAppPayResponse execute = alipayClient.sdkExecute(request);
            log.info("[AlipayTradeAppPayResponse] " + JsonTools.beanToJson(execute));
            return response(execute);
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 手机网站支付接口2.0
     * <a href='https://docs.open.alipay.com/api_1/alipay.trade.wap.pay/'>文档</a>
     *
     * @param model <br>
     *              - subject [Y]	商品的标题/交易标题/订单标题/订单关键字等。<br>
     *              - out_trade_no [Y] 商户网站唯一订单号<br>
     *              - total_amount [Y] 订单总金额，单位为元，精确到小数点后两位，取值范围[0.01,100000000]<br>
     *              - timeout_express 该笔订单允许的最晚付款时间，逾期将关闭交易。取值范围：1m～15d。m-分钟，h-小时，d-天，1c-当天（1c-当天的情况下，无论交易何时创建，都在0点关闭）。 该参数数值不接受小数点， 如 1.5h，可转换为 90m。<br>
     *              - product_code 销售产品码，商家和支付宝签约的产品码<br>
     *              - body 对一笔交易的具体描述信息。如果是多种商品，请将商品描述字符串累加传给body。<br>
     *              - time_expire 绝对超时时间，格式为yyyy-MM-dd HH:mm。<br>
     *              - goods_type 商品主类型 :0-虚拟类商品,1-实物类商品<br>
     *              - promo_params 优惠参数 注：仅与支付宝协商后可用<br>
     *              - quit_url 用户付款中途退出返回商户网站的地址
     *              - passback_params 公用回传参数，如果请求时传递了该参数，则返回给商户时会回传该参数。支付宝只会在同步返回（包括跳转回商户网站）和异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝。例如:merchantBizType%3d3C%26merchantBizNo%3d2016010101111<br>
     *              - business_params 	商户传入业务信息，具体值要和支付宝约定，应用于安全，营销等参数直传场景，格式为json格式 例如:{"data":"123"}<br>
     * @return
     */
    public Message<AlipayTradeWapPayResponse> tradeWapPay(AlipayTradeWapPayModel model) {
        AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
        request.setBizModel(model);
        request.setNotifyUrl(alipayConfig.getNotifyUrl());
        try {
            if (isDebug) {
                log.debug("[Pay Alipay] refund tradeWapPay:" + JsonTools.beanToJson(model));
            }
            request.setNeedEncrypt(alipayConfig.needEncrypt());
            return response(alipayClient.sdkExecute(request));
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 交易查询
     * <a href='https://docs.open.alipay.com/api_1/alipay.trade.query'>文档</a>
     *
     * @param model <br>
     *              - out_trade_no 支付时传入的商户订单号，与 trade_no 必填一个<br>
     *              - trade_no     支付时返回的支付宝交易号，与 out_trade_no 必填一个<br>
     * @return
     */
    public Message<AlipayTradeQueryResponse> tradeQuery(AlipayTradeQueryModel model) {
        if (isBlank(model.getOutTradeNo()) && isBlank(model.getTradeNo())) {
            return Message.fail("out_trade_no和trade_no必传一项");
        }
        try {
            if (isDebug) {
                log.debug("[Pay Alipay] refund tradeQuery:" + JsonTools.beanToJson(model));
            }
            AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();//创建API对应的request类
            request.setNeedEncrypt(alipayConfig.needEncrypt());
            request.setBizModel(model);
            return response(alipayClient.sdkExecute(request));
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 退款
     * <a href='https://docs.open.alipay.com/api_1/alipay.trade.refund'>文档</a>
     *
     * @param model <br>
     *              - out_trade_no   支付时传入的商户订单号，与 trade_no 必填一个 <br>
     *              - trade_no       支付时返回的支付宝交易号，与 out_trade_no 必填一个 <br>
     *              - out_request_no 本次退款请求流水号，部分退款时必传 <br>
     *              - refund_amount  本次退款金额 <br>
     * @return
     */
    public Message<AlipayTradeRefundResponse> refund(AlipayTradeRefundModel model) {
        if (isBlank(model.getOutTradeNo()) && isBlank(model.getTradeNo())) {
            return Message.fail("out_trade_no和trade_no必传一项");
        }
        if (isBlank(model.getOutRequestNo()) && isBlank(model.getRefundAmount())) {
            return Message.fail("out_request_no和refund_amount不能为空");
        }
        try {
            if (isDebug) {
                log.debug("[Pay Alipay] refund params:" + JsonTools.beanToJson(model));
            }
            AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();//创建API对应的request类
            request.setNeedEncrypt(alipayConfig.needEncrypt());
            request.setBizModel(model);
            return response(alipayClient.sdkExecute(request));
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 退款查询
     * <a href='https://docs.open.alipay.com/api_1/alipay.trade.fastpay.refund.query/'>文档</a>
     *
     * @param model<br> - trade_no 支付宝交易号，和商户订单号不能同时为空
     *                  - out_trade_no 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取trade_no
     *                  - out_request_no [Y] 请求退款接口时，传入的退款请求号，如果在退款请求时未传入，则该值为创建交易时的外部交易号
     *                  - org_pid 银行间联模式下有用，其它场景请不要使用；双联通过该参数指定需要查询的交易所属收单机构的pid;
     * @return
     */
    public Message<AlipayTradeFastpayRefundQueryResponse> refundQuery(AlipayTradeFastpayRefundQueryModel model) {
        if (isBlank(model.getOutTradeNo()) && isBlank(model.getTradeNo())) {
            return Message.fail("out_trade_no和trade_no必传一项");
        }
        if (StringTools.isBlank(model.getOutRequestNo())) {
            return Message.fail("out_request_no不能为空");
        }
        try {
            if (isDebug) {
                log.debug("[Pay Alipay] refundQuery params:" + JsonTools.beanToJson(model));
            }
            AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
            request.setNeedEncrypt(alipayConfig.needEncrypt());
            return response(alipayClient.sdkExecute(request));
        } catch (AlipayApiException e) {
            return Message.fail(e.getMessage());
        }
    }

    /**
     * 异步通知处理
     * <a href='https://docs.open.alipay.com/204/105301/'>文档</a>
     * <p>
     * 参数示例:
     * <ul>
     * <li>app_id=2015102700040153</li>
     * <li>seller_id=2088102119685838</li>
     * <li>body=大乐透2.1</li>
     * <li>total_amount=2.00</li>
     * <li>buyer_id=2088102116773037</li>
     * <li>trade_no=2016071921001003030200089909</li>
     * <li>refund_fee=0.00</li>
     * <li>notify_time=2016-07-19 14:10:49</li>
     * <li>subject=大乐透2.1</li>
     * <li>sign_type=RSA2</li>
     * <li>charset=utf-8</li>
     * <li>notify_type=trade_status_sync</li>
     * <li>out_trade_no=0719141034-6418</li>
     * <li>gmt_close=2016-07-19 14:10:46</li>
     * <li>gmt_payment=2016-07-19 14:10:47</li>
     * <li>trade_status=TRADE_SUCCESS</li>
     * <li>version=1.0</li>
     * <li>sign=kPbQIjX+xQc8F0/A6/AocELIjhhZnGbcBN6G4MM/HmfWL4ZiHM6fWl5NQhzXJusaklZ1LFuMo+lHQUELAYeugH8LYFvxnNajOvZhuxNFbN2LhF0l/KL8ANtj8oyPM4NN7Qft2kWJTDJUpQOzCzNnV9hDxh5AaT9FPqRS6ZKxnzM=</li>
     * <li>gmt_create=2016-07-19 14:10:44</li>
     * <li>notify_id=4a91b7a78a503640467525113fb7d8bg8e</li>
     * </ul>
     *
     * </p>
     *
     * @param request
     * @param alipayHandle
     * @throws Exception
     */
    public <T> void payCallBack(HttpServletRequest request, HttpServletResponse response, AlipayHandle<T> alipayHandle) throws Exception {
        Map<String, String> params = ServletTools.convertRequestParamsToMap(request);
        if (isDebug) {
            log.debug("[Pay Alipay] payCallBack params:" + JsonTools.beanToJson(params));
        }
        alipayHandle.onMessage(params);
        boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.getAlipayPublicKey(), alipayConfig.getCharset(), alipayConfig.getSignType());
        if (signVerified) {
            T t = check(alipayHandle, params);
            executor.execute(() -> alipayHandle.business(t, params));
            ServletTools.printString(response, "success");
        } else {
            log.error("[alipay callback] sign error");
            ServletTools.printString(response, "failure");
        }
    }

    /**
     * 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号，
     * 2、判断total_amount是否确实为该订单的实际金额（即商户订单创建时的金额），
     * 3、校验通知中的seller_id（或者seller_email)是否为out_trade_no这笔单据的对应的操作方（有的时候，一个商户可能有多个seller_id/seller_email），
     * 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过，则表明本次通知是异常通知，务必忽略。
     * 在上述验证通过后商户必须根据支付宝不同类型的业务通知，正确的进行不同的业务处理，并且过滤重复的通知结果数据。
     * 在支付宝的业务通知中，只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时，支付宝才会认定为买家付款成功。
     *
     * @param params
     * @throws AlipayApiException
     */
    private <T> T check(AlipayHandle<T> alipayHandle, Map<String, String> params) throws AlipayApiException {
        // 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号，
        T t = alipayHandle.getByOutTradeNo(params.get("out_trade_no")); // 这个方法自己实现
        if (t == null) {
            throw new AlipayApiException("out_trade_no错误");
        }

        // 2、判断total_amount是否确实为该订单的实际金额（即商户订单创建时的金额），
        if (!alipayHandle.checkOrderByAmount(t, new BigDecimal(params.get("total_amount")))) {
            throw new AlipayApiException("error total_amount:" + new BigDecimal(params.get("total_amount")));
        }

        // 3、校验通知中的seller_id（或者seller_email)是否为out_trade_no这笔单据的对应的操作方（有的时候，一个商户可能有多个seller_id/seller_email），
        if (!alipayHandle.checkOrderBySellerId(t, params.get("seller_id"))) {
            throw new AlipayApiException("error seller_id");
        }

        // 4、验证app_id是否为该商户本身。
        if (!params.get("app_id").equals(alipayConfig.getAppid())) {
            throw new AlipayApiException("seller_id app_id");
        }
        return t;
    }

    /**
     * 集中处理返回
     *
     * @param response
     * @param <T>
     * @return
     */
    public <T extends AlipayResponse> Message<T> response(T response) {
        if (response.isSuccess()) {
            if (isDebug) {
                log.debug("[Pay Alipay] response:" + JsonTools.beanToJson(response));
            }
            return Message.successResult(response);
        } else {
            log.error("支付宝错误:{}", JsonTools.beanToJson(response));
            return Message.fail(StringTools.format("支付宝错误:{0}", response.getCode()));
        }
    }

    @Override
    public void afterPropertiesSet() {
        if (alipayConfig.needEncrypt()) {
            alipayClient = new DefaultAlipayClient(alipayConfig.getGatewayUrl(), alipayConfig.getAppid(), alipayConfig.getPrivateKey(), "json", alipayConfig.getCharset(), alipayConfig.getAlipayPublicKey(), alipayConfig.getSignType(), alipayConfig.getAesKey(), "AES");
        } else {
            alipayClient = new DefaultAlipayClient(alipayConfig.getGatewayUrl(), alipayConfig.getAppid(), alipayConfig.getPrivateKey(), "json", alipayConfig.getCharset(), alipayConfig.getAlipayPublicKey(), alipayConfig.getSignType());
        }
        this.isDebug = this.environment.getProperty("ajavaer.debug", Boolean.class, true);
        if (isDebug) {
            log.debug("Pay Alipay init");
        }
    }
}